#include <render/renderer.h>
#include <graphics/image2d.h>
#include <graphics/image3d.h>
#include <QtOpenGL>
#include <render/glextensions.h>
#include <graphics/utils.h>


namespace yam3d
{
namespace render
{

    namespace
    {
        static const int WRAPPING_LOOKUP[] = { GL_REPEAT, GL_CLAMP };
        static const int FILTER_LOOKUP[] = { GL_NEAREST, GL_LINEAR,
                                GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR,
                                GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR };
        static const int SAMPLER_UNIT_LOOKUP[] = { GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2 };
    }

    Renderer::Renderer()
    : core::Object(__FUNCTION__)
    {
    }

    Renderer::~Renderer()
    {
    }

    void Renderer::applyCamera( const math::mat4& matView, const math::mat4& matProj )
    {
        m_matView = matView;
        m_matProj = matProj;
    }

    void Renderer::applyPass(const graphics::Pass* pass )
    {
    //    qDebug(" applyPass begin");
        m_pass = pass;

       // assert(m_pass->getCamera() != 0);
        assert( m_pass != 0 );
        assert(m_pass->getShaderProgram() != 0);

   //      qDebug("  m_pass->getShaderProgram()->bind()");
        m_pass->getShaderProgram()->bind();
     //   qDebug("  setRenderState(m_pass->getRenderState());");
        setRenderState(m_pass->getRenderState());

        if( m_pass->getShaderUniforms() )
        {
       //     qDebug(" m_pass->getShaderUniforms()->applyValues(m_pass->getShaderProgram());");
            m_pass->getShaderUniforms()->applyValues(m_pass->getShaderProgram());
        }


      //  qDebug("  m_pass->getShaderProgram()->setUniformValue = vecViewportSize");
        m_pass->getShaderProgram()->setUniformValue( "vecViewportSize", m_viewport.getSize() );
       //  qDebug("  m_pass");
        assert(m_viewport.getSize().x() > 0.1f);
        assert(m_viewport.getSize().y() > 0.1f);
        math::vec2 vInvViewportSize(1.0f/m_viewport.getSize().x(), 1.0f/m_viewport.getSize().y() );
//        qDebug("  m_pass->getShaderProgram()->setUniformValue = vecInvViewportSize");

        m_pass->getShaderProgram()->setUniformValue( "vecInvViewportSize", vInvViewportSize );

  //       qDebug("pass->getSamplerCount() = %d",pass->getSamplerCount());
        for( int i=0; i<pass->getSamplerCount(); ++i )
        {
            const render::Texture2D* texture = dynamic_cast<const render::Texture2D*>(pass->getTexture(i));
            assert( texture != 0 );
      //      qDebug("1");
            assert( pass->getSamplerState(i) != 0 );
            texture->bind(pass->getSamplerState(i),i);
      //      qDebug("2");
            assert(pass->getSamplerName(i).length() > 0);
            m_pass->getShaderProgram()->setUniformValue(pass->getSamplerName(i).toStdString().c_str(), i );
      //      qDebug("3");
        }
    }

    void Renderer::render(const graphics::Mesh* mesh, const math::mat4& matModel )
    {
        assert(mesh != 0);
        assert(m_pass != 0);

        // Check camera ref value
        assert( m_pass->getCameraRef() == "MAIN_CAMERA" || m_pass->getCameraRef() == "NO_CAMERA" );

        if( m_pass->getCameraRef() == "MAIN_CAMERA" )
        {
            // Case, where pass main camera is used. Main camera is set by applyCamera-method.
            // Set come camera related uniforms.
            math::mat4 matWorldView = m_matView.inverted();
            math::mat4 matModelView = matWorldView * matModel;
            math::mat4 matModelViewInverseTranspose = matModelView.inverted().transposed();
            math::mat4 matModelViewProj = m_matProj * matModelView;
            math::vec4 vecEyePositionWorld = m_matView * math::vec4(0,0,0,1);

            m_pass->getShaderProgram()->setUniformValue("vecEyePositionWorld", vecEyePositionWorld.toVector3D() );
            m_pass->getShaderProgram()->setUniformValue("matModel", matModel );
            m_pass->getShaderProgram()->setUniformValue("matView", m_matView );
            m_pass->getShaderProgram()->setUniformValue("matProj", m_matProj );
            m_pass->getShaderProgram()->setUniformValue("matWorldView", matWorldView );
            m_pass->getShaderProgram()->setUniformValue("matModelView", matModelView );
            m_pass->getShaderProgram()->setUniformValue("matModelViewInverseTranspose", matModelViewInverseTranspose );
            m_pass->getShaderProgram()->setUniformValue("matModelViewProj", matModelViewProj );

        }
        else if ( m_pass->getCameraRef() == "NO_CAMERA" )
        {
            // Case, where pass doest not have camera.
            // Because pass does not use camera, make sure that shader does not have certain
            // camera related uniforms. This easiest detecting of errors.
            assert( m_pass->getShaderProgram()->uniformLocation("vecEyePositionWorld") == -1 );
            assert( m_pass->getShaderProgram()->uniformLocation("matView") == -1 );
            assert( m_pass->getShaderProgram()->uniformLocation("matProj") == -1 );
            assert( m_pass->getShaderProgram()->uniformLocation("matWorldView") == -1 );
            assert( m_pass->getShaderProgram()->uniformLocation("matModelView") == -1 );
            assert( m_pass->getShaderProgram()->uniformLocation("vecEyePositionWorld") == -1 );
            assert( m_pass->getShaderProgram()->uniformLocation("matModelViewInverseTranspose") == -1 );
            assert( m_pass->getShaderProgram()->uniformLocation("matModelViewProj") == -1 );

            m_pass->getShaderProgram()->setUniformValue("matModel", matModel );
        }
        else
        {
            assert(0); // Should be error
        }

        mesh->getVertexBuffer()->setVertexArrays(m_pass->getShaderProgram());
        mesh->getIndexBuffer()->draw();
    }

     void Renderer::setViewport( int x, int y, int sizeX, int sizeY )
     {
         glViewport( x, y, sizeX, sizeY );
         m_viewport.setSize( math::vec2(sizeX,sizeY) );
         m_viewport.setStart( math::vec2(x,y) );

     }

    void Renderer::setRenderState(const graphics::RenderState* rs)
    {
        assert( rs != 0 );
        if( rs->isDepthEnabled() )
        {
            glEnable(GL_DEPTH_TEST);
            glDepthMask(GL_TRUE);
        }
        else
        {
            glDisable(GL_DEPTH_TEST);
            glDepthMask(GL_FALSE);
        }

        switch( rs->getCullMode() )
        {
        case graphics::RenderState::CULL_BACK:
            glEnable(GL_CULL_FACE);
            glCullFace(GL_BACK);
            break;

        case graphics::RenderState::CULL_FRONT:
            glEnable(GL_CULL_FACE);
            glCullFace(GL_FRONT);
            break;

        case graphics::RenderState::CULL_NONE:
            glDisable(GL_CULL_FACE);
            break;

        default:
            assert(0);
            break;
        }
    }


    PerMaterialSortedRenderList::PerMaterialSortedRenderList()
    : core::Object(__FUNCTION__)
    {
    }

    PerMaterialSortedRenderList::~PerMaterialSortedRenderList()
    {
    }

    void PerMaterialSortedRenderList::add( const graphics::Effect* effect, const graphics::Mesh* mesh, const math::mat4& transform )
    {
        assert( effect != 0 );
        assert( mesh != 0 );
        m_renderLists[effect].append( new MeshTransform(mesh,transform));
    }

    void PerMaterialSortedRenderList::render(Renderer* renderer, bool print) const
    {
        for( HashType::const_iterator it = m_renderLists.begin(); it != m_renderLists.end(); ++it )
        {
            if ( print ) qDebug( "Render Outer" );
            assert( renderer != 0 );
            const graphics::Effect* effect = it.key();
            assert( effect != 0 );

            const MeshArray& meshes = it.value();

            for( int passIndex=0; passIndex<effect->getPassCount(); ++passIndex )
            {
                if ( print ) qDebug( "Render Pass begin. meshes.size() = %d", meshes.size() );
                assert( effect->getPass(passIndex) != 0 );
                renderer->applyPass( effect->getPass(passIndex) );

                for( int meshIndex=0; meshIndex<meshes.size(); ++meshIndex )
                {
                    if ( print ) qDebug( "Render Mesh begin" );
                    assert( meshes[meshIndex] != 0 );
                    assert( meshes[meshIndex]->m_mesh != 0 );
                    renderer->render(meshes[meshIndex]->m_mesh, meshes[meshIndex]->m_transform);
                    if ( print ) qDebug( "Render Mesh end" );
                }

                if ( print ) qDebug( "Render Pass end" );
            }
            if ( print ) qDebug( "Render Outer end" );
        }
    }


    UnsortedRenderList::UnsortedRenderList()
    : core::Object(__FUNCTION__)
    {
    }

    UnsortedRenderList::~UnsortedRenderList()
    {
    }

    void UnsortedRenderList::add( const graphics::Effect* effect, const graphics::Mesh* mesh, const math::mat4& transform )
    {
        assert( effect != 0 );
        assert( mesh != 0 );
        m_renderLists.append( new MeshTransform(effect,mesh,transform));
    }

    void UnsortedRenderList::render(Renderer* renderer) const
    {
        assert( renderer != 0 );
        for( int i=0; i<m_renderLists.size(); ++i )
        {
            const graphics::Effect* effect = m_renderLists[i]->m_effect;
            assert( effect != 0 );

            for( int passIndex=0; passIndex<effect->getPassCount(); ++passIndex )
            {
                renderer->applyPass( effect->getPass(passIndex) );
                renderer->render(m_renderLists[i]->m_mesh, m_renderLists[i]->m_transform);
            }
        }
    }

}
}

